/*
 * Copyright (C) 2008-2010 Oliver Horn.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.funcj.tuples;

import javax.annotation.Nullable;

/**
 * Helper functions that deal with {@code Tuple} objects.
 *
 * @see Tuple
 * @see Pairs
 *
 * @author Oliver Horn
 */
public final class Tuples {

	private Tuples() {
	}

	/**
	 * Creates a tuple from the given elements.
	 *
	 * Calling this method with more than 4 elements will return a generic
	 * implementation of the {@link Tuple} interface.
	 *
	 * @param elements the elements
	 * @return the created tuple.
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static Tuple newTuple(Object...elements) {
		switch (elements.length) {
		case 0:
			return Unit.UNIT;
		case 1:
			return new Single(elements[0]);
		case 2:
			return new Pair(elements[0], elements[1]);
		case 3:
			return new Triple(elements[0], elements[1], elements[2]);
		case 4:
			return new Quadruple(elements[0], elements[1], elements[2], elements[3]);
		default:
			return new NTuple(elements);
		}
	}

	/**
	 * Returns the empty tuple.
	 * @return the empty tuple
	 * @see Unit#UNIT
	 */
	public static Unit newTuple() {
		return Unit.UNIT;
	}

	/**
	 * Creates a single from the given elements.
	 * @return the created single.
	 * @see Single
	 */
	public static <A> Single<A> newTuple(@Nullable A first) {
		return new Single<A>(first);
	}

	/**
	 * Creates a pair from the given elements.
	 * @return the created pair.
	 * @see Pair
	 */
	public static <A, B> Pair<A, B> newTuple(@Nullable A first, @Nullable B second) {
		return new Pair<A, B>(first, second);
	}

	/**
	 * Creates a triple from the given elements.
	 * @return the created triple.
	 * @see Triple
	 */
	public static <A, B, C> Triple<A, B, C> newTuple(@Nullable A first, @Nullable B second, @Nullable C third) {
		return new Triple<A, B, C>(first, second, third);
	}

	/**
	 * Creates a quadruple from the given elements.
	 * @return the created quadruple.
	 * @see Quadruple
	 */
	public static <A, B, C, D> Quadruple<A, B, C, D> newTuple(@Nullable A first, @Nullable B second, @Nullable C third, @Nullable D fourth) {
		return new Quadruple<A, B, C, D>(first, second, third, fourth);
	}

	/**
	 * Returns the first element of the given tuple.
	 * @return the first element
	 * @throws IllegalArgumentException if the tuple's arity is 0
	 */
	public static Object first(Tuple tuple) throws IllegalArgumentException {
		if (tuple.arity() < 1) {
			throw new IllegalArgumentException();
		}
		return tuple.toArray()[0];
	}

	/**
	 * Returns the first element of the given single.
	 * @return the first element
	 */
	public static <A> A first(Single<A> single) {
		return single.first();
	}

	/**
	 * Returns the first element of the given pair.
	 * @return the first element
	 */
	public static <A> A first(Pair<A, ?> pair) {
		return pair.first();
	}

	/**
	 * Returns the first element of the given triple.
	 * @return the first element
	 */
	public static <A> A first(Triple<A, ?, ?> triple) {
		return triple.first();
	}

	/**
	 * Returns the first element of the given quadruple.
	 * @return the first element
	 */
	public static <A> A first(Quadruple<A, ?, ?, ?> quadruple) {
		return quadruple.first();
	}

	/**
	 * Returns the second element of the given tuple.
	 * @return the second element
	 * @throws IllegalArgumentException if the tuple's arity is less than 2
	 */
	public static Object second(Tuple tuple) throws IllegalArgumentException {
		if (tuple.arity() < 2) {
			throw new IllegalArgumentException();
		}
		return tuple.toArray()[1];
	}

	/**
	 * Returns the second element of the given tuple.
	 * @return the second element
	 */
	public static <B> B second(Pair<?, B> tuple) {
		return tuple.second();
	}

	/**
	 * Returns the second element of the given tuple.
	 */
	public static <B> B second(Triple<?, B, ?> tuple) {
		return tuple.second();
	}

	/**
	 * Returns the second element of the given tuple.
	 */
	public static <B> B second(Quadruple<?, B, ?, ?> tuple) {
		return tuple.second();
	}


	/**
	 * Returns the third element of the given tuple.
	 * @return the third element
	 * @throws IllegalArgumentException if the tuple's arity is less than 3
	 */
	public static Object third(Tuple tuple) throws IllegalArgumentException {
		if (tuple.arity() < 3) {
			throw new IllegalArgumentException();
		}
		return tuple.toArray()[2];
	}

	/**
	 * Returns the fourth element of the given tuple.
	 */
	public static <C> C third(Triple<?, ?, C> tuple) {
		return tuple.third();
	}

	/**
	 * Returns the fourth element of the given tuple.
	 */
	public static <C> C third(Quadruple<?, ?, C, ?> tuple) {
		return tuple.third();
	}

	/**
	 * Returns the fourth element of the given tuple.
	 * @return the fourth element
	 * @throws IllegalArgumentException if the tuple's arity is less than 4
	 */
	public static Object fourth(Tuple tuple) throws IllegalArgumentException {
		if (tuple.arity() < 4) {
			throw new IllegalArgumentException();
		}
		return tuple.toArray()[3];
	}

	/**
	 * Returns the fourth element of the given tuple.
	 */
	public static <D> D fourth(Quadruple<?, ?, ?, D> tuple) {
		return tuple.fourth();
	}
}
